using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.IO;
using System.Threading;
using System.Windows.Forms;
using Lizk.SimpleHUD;
using Microsoft.Xna.Framework.Graphics;
using Lizk.Utilities;

namespace SharpMedia.MDX
{
    /// <summary>
    /// A Texture2D wrapper class that enables asynchronous load of Textures
    /// </summary>
    public class XNAImage : IDisposable
    {
        private GraphicsDevice device;
        private Texture2D texture;
        private string textureFile;
        private Size maxSize;
        private Rectangle textureBounds;
        private bool isLoaded = false;
        private bool disposed = false;

        /// <summary>
        /// Event triggered when the texture has been loaded
        /// </summary>
        public event EventHandler TextureLoaded;        
        /// <summary>
        /// Path to the file this image was loaded from
        /// </summary>
        public string TexturePath { get { return textureFile; } }
        /// <summary>
        /// Is the image done loading
        /// </summary>
        public bool IsLoaded { get { return isLoaded; } }

        /// <summary>
        /// The part of the Texture2D object that is actually the image. This is needed to provide PowerOf2 Compatibility
        /// </summary>
        public Rectangle SourceRectangle { get { return textureBounds; } }

        /// <summary>
        /// The Texture
        /// </summary>
        public Texture2D Texture { get { return texture; } set { texture = value; } }

        /// <summary>
        /// Is this Image disposed
        /// </summary>
        public bool Disposed { get { return disposed; } }


        /// <summary>
        /// Initializes a new instance of the <see cref="XNAImage"/> class.
        /// </summary>
        /// <param name="d">The graphics device.</param>
        /// <param name="file">The file.</param>
        /// <param name="eh">The eventhandler to be called when done loading.</param>
        private XNAImage(GraphicsDevice device, string file, EventHandler loadCompleteHandler)
        {
            this.device = device;
            textureFile = file;
            TextureLoaded += loadCompleteHandler;
        }

        // dispose the unmanaged resources upon finalization
        ~XNAImage()
        {
            Dispose();
        }

        /// <summary>
        /// Dispose the texture
        /// </summary>
        public void Dispose()
        {
            if (!disposed)
            {
                if (texture != null && !texture.IsDisposed)
                {
                    texture.Dispose();
                    texture = null;
                }
                disposed = true;
            }
        }

        public static XNAImage FromFile(GraphicsDevice device, string file, EventHandler eh, Size maxSize)
        {
            Monitor.Enter(MDXImageLoader.loadQueue);
            XNAImage image = new XNAImage(device, file, eh);
            image.maxSize = maxSize;

            MDXImageLoader.loadQueue.Enqueue(image);
            Monitor.Pulse(MDXImageLoader.loadQueue);
            Monitor.Exit(MDXImageLoader.loadQueue);
            return image;
        }

        /// <summary>
        /// The worker class, this one contains a thread and a queue, 
        /// </summary>
        private static class MDXImageLoader
        {
            internal static bool theEnd = false;
            internal static Queue<XNAImage> loadQueue = new Queue<XNAImage>();

            private static Thread loadWorker = null;

            static MDXImageLoader()
            {
                loadWorker = new Thread(new ThreadStart(ProcessQueue));
                loadWorker.Name = "MDXImage Worker";
                loadWorker.IsBackground = true;
                loadWorker.Priority = ThreadPriority.BelowNormal;
                loadWorker.Start();
                Application.ApplicationExit += new EventHandler(Application_ApplicationExit);
            }

            static void Application_ApplicationExit(object sender, EventArgs e)
            {
                // Wake up the thread for it to finish!
                theEnd = true;
                Monitor.Enter(loadQueue);
                loadQueue.Clear();
                Monitor.PulseAll(loadQueue);
                Monitor.Exit(loadQueue);
            }



            private static void ProcessQueue()
            {
                Queue<XNAImage> tmpQueue = new Queue<XNAImage>();
                while (true)
                {
                    Monitor.Enter(loadQueue);
                    while (loadQueue.Count == 0)
                    {
                        if (theEnd)
                        {
                            Monitor.Exit(loadQueue);
                            return;
                        }
                        else
                        {
                            Monitor.Wait(loadQueue);
                        }
                    }

                    while(loadQueue.Count > 0) 
                    tmpQueue.Enqueue(loadQueue.Dequeue());
                    Monitor.Pulse(loadQueue);
                    Monitor.Exit(loadQueue);

                    while (tmpQueue.Count > 0)
                    {
                        XNAImage image = tmpQueue.Dequeue();
                        if (!image.isLoaded)
                            LoadTexture(image);
                    }
                }
            }

            private static int Pow2Adjust(int i)
            {
                return (int)Math.Pow(2, Math.Round(Math.Log(i, 2)));
            }

            private static void LoadTexture(object o)
            {
                LoadTexture((XNAImage)o);
            }


            private static void LoadTexture(XNAImage image)
            {
                if (string.IsNullOrEmpty(image.textureFile))
                    return;

                FileStream fs = File.OpenRead(image.textureFile);
                Image i = Image.FromStream(fs, false, true);
                image.textureBounds = new Rectangle(Point.Empty, i.Size);
                
                // If we need the texture to be power of two, just resize the texture and save which part is the 
                if (image.device.GraphicsDeviceCapabilities.TextureCapabilities.RequiresPower2)
                {
                    RectangleS size = HUDImage.ResizeConstrained(true, new SizeS(i.Width, i.Height), new SizeS(Pow2Adjust(i.Width),Pow2Adjust(i.Height)));
                    i = ImageTools.Resize(i, new Size((int)size.Width, (int)size.Height), true, System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic);
                    image.textureBounds = new Rectangle(0, 0, (int)size.Width, (int)size.Height);
                }


                fs.Seek(0, SeekOrigin.Begin);
                try
                {
                    
                    image.texture = Texture2D.FromFile(image.device, fs, new TextureCreationParameters(i.Size.Width, i.Size.Height, 1, 1, SurfaceFormat.Color, TextureUsage.None, new Microsoft.Xna.Framework.Graphics.Color(255,0,255), FilterOptions.Linear, FilterOptions.Linear));  //TextureLoader.FromStream(image.device, fs, i.Size.Width, i.Size.Height, 2, Usage.None, Format.A8R8G8B8, Pool.Managed, Filter.Linear, Filter.Linear, 0);
                }
                catch (Exception e)
                {
                    Debug.WriteLine("Image Not loaded: " + e.Message);
                }
                i.Dispose();
                fs.Dispose();

                if (image.texture != null)
                {
                    image.isLoaded = true;
                    if (image.TextureLoaded != null)
                        image.TextureLoaded(image, null);
                }
            }


        }
    }




}